#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <libgen.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include "sqlite3.h"
#include "HY1C_out.h"
#include "hy1c_util.h"

#ifdef __cplusplus
extern "C" {
#endif

#include "sqlite3_opt.h"
#include "l2_struc.h"
#include "config.h"
#ifdef __cplusplus
}
#endif

static sqlite3 *db = nullptr;

static int point_index = 0;

int init_db(const char * db_name)
{
	if (db_name == NULL)
	{
		char report_db[PATH_MAXSIZE] = {0};
		char *p = strdup(g_cfg.out_gain);
		char *q = p + strlen(p);
		while(q-- != p) {
		if (*q == '.')
		    break;
		}

		strncat(report_db, p, q - p);
		strcat(report_db, "_report.db");
		db_name = report_db;
		free(p);
	}

	char * err_msg = nullptr;

	unlink(db_name);

	//int ret = sqlite3_open(db_name, &db);
	int ret = sqlite3_open(":memory:", &db);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("Open database failed. %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}

	ret = sqlite3_exec(db, "PRAGMA synchronous = OFF; ", 0, 0, 0);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("set synchronous failed. %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}

	char *sql = "CREATE TABLE report(\
		     ID INTEGER,\
		     Wave INTEGER,\
		     scan_hy1c INTEGER,\
		     pix_hy1c INTEGER,\
		     scan_modis INTEGER,\
		     pix_modis INTEGER,\
		     lat REAL,\
		     lon REAL,\
		     gain_orig REAL,\
		     Lt_theroy REAL,\
		     Lt REAL,\
		     Lt_modis REAL,\
		     Lr REAL,\
		     La REAL,\
		     Tlg REAL,\
		     tLf REAL,\
		     Lw_orig REAL,\
		     Lw REAL,\
		     tg_sol REAL,\
		     tg_sen REAL,\
		     t_sol REAL,\
		     t_sen REAL,\
		     sola REAL,\
		     sena REAL,\
		     solz REAL,\
		     senz REAL,\
		     phase REAL,\
		     aot_orig REAL,\
		     aot REAL,\
		     aermodnum INTEGER,\
		     aermodname TEXT,\
		     flags INTEGER,\
		     hy1c_invalid INTEGER,\
		     modis_invalid INTEGER\
		     );";

	ret = sqlite3_exec(db, sql, 0, 0, &err_msg);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("create table failed %s\n", err_msg);
		sqlite3_free(err_msg);
		sqlite3_close(db);
		return -1;
	}

	sql = "CREATE TABLE gain_report(\
		     ID INTEGER PRIMARY KEY,\
		     Wave INTEGER,\
		     scan_hy1c INTEGER,\
		     pix_hy1c INTEGER,\
		     gain REAL\
		     );";

	ret = sqlite3_exec(db, sql, 0, 0, &err_msg);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("create table failed %s\n", err_msg);
		sqlite3_free(err_msg);
		sqlite3_close(db);
		return -1;
	}
	return 0;
}

static void reset_point_index(void)
{
    point_index = 0;
}

void close_db(void)
{
    reset_point_index();
	int ret = sqlite3_close(db);
    if (ret != SQLITE_OK)
        LOG_ERR("closedb %s\n", sqlite3_errmsg(db));
    db = nullptr;
}

int insert_report(l2str *hy1c, l2str *modis, float * wave, size_t nwave)
{
	// report only
	//float Lt_modis[MAX_WAVE] = {0};
	//if (modis && (modis->Lt != NULL)) {
	//	line_interpolation_float(g_cfg.wave_hy1c, g_cfg.hy1c_nwave,
	//			     g_cfg.wave_modis, g_cfg.modis_nwave,
	//			     modis->Lt, Lt_modis);
	//}

	int ret = 0;
	char * err_msg = nullptr;


	sqlite3_stmt *stmt;
	const char * sql = "INSERT INTO 'report' VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);

	point_index++;
	for (size_t i = 0; i < nwave; i++) {
		sqlite3_reset(stmt);
		int column = 0;
		sqlite3_bind_int(stmt, ++column, point_index);
		sqlite3_bind_int(stmt, ++column, (int)wave[i]);
		sqlite3_bind_int(stmt, ++column, hy1c->iscan);
		sqlite3_bind_int(stmt, ++column, hy1c->ipix);

		if (modis) {
			sqlite3_bind_int(stmt, ++column, modis->iscan);
			sqlite3_bind_int(stmt, ++column, modis->ipix);
		} else {
			sqlite3_bind_int(stmt, ++column, 0);
			sqlite3_bind_int(stmt, ++column, 0);
		}
		sqlite3_bind_double(stmt, ++column, *(hy1c->lat));
		sqlite3_bind_double(stmt, ++column, *(hy1c->lon));
		sqlite3_bind_double(stmt, ++column, hy1c->gain[i]); // gain
		sqlite3_bind_double(stmt, ++column, hy1c->Lt_theroy[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->Lt[i]);
		sqlite3_bind_double(stmt, ++column, modis->Lt[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->Lr[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->La[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->TLg[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->tLf[i]);
		if (g_cfg.DPG_method == DPG_TRBMC)
			sqlite3_bind_double(stmt, ++column, (float)(hy1c->field_Lw[i]));
		else
			sqlite3_bind_double(stmt, ++column, modis->Lw[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->Lw[i]); // Lw
		sqlite3_bind_double(stmt, ++column, hy1c->tg_sol[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->tg_sen[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->t_sol[i]);
		sqlite3_bind_double(stmt, ++column, hy1c->t_sen[i]);
		sqlite3_bind_double(stmt, ++column, *(hy1c->sola));
		sqlite3_bind_double(stmt, ++column, *(hy1c->sena));
		sqlite3_bind_double(stmt, ++column, *(hy1c->solz));
		sqlite3_bind_double(stmt, ++column, *(hy1c->senz));
		sqlite3_bind_double(stmt, ++column, *(hy1c->phase)); //phase
		if (g_cfg.DPG_method == DPG_TRBMC)
			sqlite3_bind_double(stmt, ++column, (float)(hy1c->field_AOT[i]));
		else
			sqlite3_bind_double(stmt, ++column, (float)(modis->taua[i]));
		sqlite3_bind_double(stmt, ++column, hy1c->aot_new[i]); // aot
		sqlite3_bind_int(stmt, ++column, *(hy1c->aerindex));
		sqlite3_bind_text(stmt, ++column, g_cfg.aermodules[(int)(*(hy1c->aerindex))], 32, NULL);

		if (modis)
			sqlite3_bind_int(stmt, ++column, *(modis->flags));
		else
			sqlite3_bind_int(stmt, ++column, 0);
		sqlite3_bind_int(stmt, ++column, hy1c->invalid[i]);

		if (modis)
			sqlite3_bind_int(stmt, ++column, modis->invalid[i]);
		else
			sqlite3_bind_int(stmt, ++column, 0);
		ret = sqlite3_step(stmt);
		if (ret != SQLITE_DONE)
		{
			LOG_ERR("failed %s\n", sqlite3_errmsg(db));
			goto finally;
		}
	}

finally:
	ret = sqlite3_finalize(stmt);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("failed %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}

#if 0
	ret = sqlite3_exec(db, "commit;", 0, 0, 0);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("failed %s\n", err_msg);
		sqlite3_free(err_msg);
		sqlite3_close(db);
		return -1;
	}
#endif
	return 0;
}

int insert_gain_report(l2rec_HY1C *hy1c, l2rec_HY1C *modis, float* wave, size_t nwave)
{
	int ret = 0;
	char * err_msg = nullptr;

	float *gain = hy1c->gain;
	int32_t *invalid = hy1c->invalid;
	size_t nscans = hy1c->nscans;
	size_t npix = hy1c->npix;

	sqlite3_stmt *stmt;
	const char * sql = "INSERT INTO 'gain_report' VALUES(?, ?, ?, ?, ?);";
	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);

	for (size_t i = 0; i <= nscans * npix * nwave - nwave; i += nwave)
	{
		for (size_t j = 0; j < nwave; j++)
		{
			if (gain[i + j] == BAD_FLT)
			{
				if (!(invalid[i + j] &= 0x40000000))
					continue;
			}

			sqlite3_reset(stmt);
			sqlite3_bind_double(stmt, 2, wave[j]);
			sqlite3_bind_double(stmt, 3, i / nwave / npix);
			sqlite3_bind_double(stmt, 4, i / nwave % npix);
			sqlite3_bind_double(stmt, 5, gain[i + j]);

			ret = sqlite3_step(stmt);
			if (ret != SQLITE_DONE)
			{
				LOG_ERR("failed %s\n", sqlite3_errmsg(db));
				goto finally;
			}
		}

	}

finally:
	ret = sqlite3_finalize(stmt);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("failed %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}
	return 0;
}

int update_report(db_item *items, const size_t count)
{
	if ((items == nullptr) || (count <= 0))
		return 0;

	std::string sql = "";
	sqlite3_stmt *stmt;

	sqlite3_exec(db, "begin transaction", 0, 0, 0);
	for (int iw = 0; iw < g_cfg.hy1c_nwave; iw++)
	{
		sql = "UPDATE report SET ";
		for (size_t i = 0; i < count; i++) {
			if (std::string(items[i].key) == "phase") {
				if (i == count - 1)
					sql += std::string(items[i].key) + " = " + std::to_string(*(float*)(items[i].value));
				else
					sql += std::string(items[i].key) + " = " + std::to_string(*(float*)(items[i].value)) + ", ";
				continue;
			}

			if (i == count - 1)
				sql += std::string(items[i].key) + " = " + std::to_string(((float*)(items[i].value))[iw]);
			else
				sql += std::string(items[i].key) + " = " + std::to_string(((float*)(items[i].value))[iw]) + ", ";

		}
		sql += " WHERE ID = (SELECT max(ID) FROM report);";

		//std::cout << sql << std::endl;
		//sqlite3_reset(stmt);
		sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, 0);
		int ret = sqlite3_step(stmt);
		if (ret != SQLITE_DONE)
		{
			LOG_ERR("failed %s\n", sqlite3_errmsg(db));
			goto finally;
		}
	}

	sqlite3_exec(db, "commit transaction", 0, 0, 0);

finally:
	int ret = sqlite3_finalize(stmt);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("failed %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}
	return 0;
}

int generate_report(void)
{
	FILE * report_f = NULL;
	char report_path[PATH_MAXSIZE] = {0};
	char *p = strdup(g_cfg.out_gain);

    const char * dname = dirname(g_cfg.out_gain);
    std::string base = std::string(basename(g_cfg.hy1c_f));
    base = base.substr(0, base.rfind('.'));
//	char *q = p + strlen(p);
//	while(q-- != p) {
//	if (*q == '.')
//		break;
//	}
//
//	strncat(report_path, p, q - p);


    static int first = 1;
    if (first && g_cfg.again.keep_original_gain) {
        sprintf(report_path, "%s/%s%s", dname, base.c_str(), "_report0.txt");
        first = 0;
    } else {
        sprintf(report_path, "%s/%s%s", dname, base.c_str(), "_report.txt");
    }

    LOG_INFO("report path is %s\n", report_path);

	report_f = fopen(report_path, "w");

	free(p);

	sqlite3_stmt *stmt = nullptr;
	const char * sql = "SELECT r.ID, r.Wave, r.scan_hy1c, r.pix_hy1c, r.scan_modis, r.pix_modis, r.lat, r.lon, g.gain, r.gain_orig, r.Lt_theroy, r.Lt, r.Lt_modis, r.Lr, r.La, r.Tlg, r.tLf, r.Lw_orig, r.Lw, r.tg_sol, r.tg_sen, r.t_sol, r.t_sen, r.sola, r.sena, r.solz, r.senz, r.phase, r.aot_orig, r.aot, r.aermodnum, r.aermodname, r.flags, r.hy1c_invalid, r.modis_invalid from gain_report as g, report as r WHERE r.scan_hy1c=g.scan_hy1c and r.pix_hy1c=g.pix_hy1c and r.Wave=g.Wave ORDER BY r.ID";
	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);
	int column_count = sqlite3_column_count(stmt);

    for (int i = 0; i < column_count; i++)
    {
        fprintf(report_f, "%s\t", sqlite3_column_name(stmt, i));
    }

    fprintf(report_f, "\n");

	while (sqlite3_step(stmt) == SQLITE_ROW)
	{
		for (int i = 0; i < column_count; i++)
		{
			switch(sqlite3_column_type(stmt, i))
			{
				case SQLITE_INTEGER:
				{
					std::string column_name = sqlite3_column_name(stmt, i);
					if (column_name == "flags" ||
					    column_name == "hy1c_invalid" ||
					    column_name == "modis_invalid")
						fprintf(report_f, "%x\t", sqlite3_column_int(stmt, i));
					else
						fprintf(report_f, "%d\t", sqlite3_column_int(stmt, i));
					break;
				}
				case SQLITE_FLOAT:
				{
					fprintf(report_f, "%0.6f\t", sqlite3_column_double(stmt, i));
					break;
				}
				case SQLITE_TEXT:
				{
					fprintf(report_f, "%s\t", sqlite3_column_text(stmt, i));
					break;
				}
				default:
				{
					//LOG_ERR("invalid data type %d\n", sqlite3_column_type(stmt, i));
					break;
				}
			}
		}
		fprintf(report_f, "\n");
	}

	int ret = sqlite3_finalize(stmt);
	if (ret != SQLITE_OK)
	{
		LOG_ERR("failed %s\n", sqlite3_errmsg(db));
		sqlite3_close(db);
		return -1;
	}

	fclose(report_f);
	return 0;
}

//int main()
//{
//	init_db("test.db");
//	return 0;
//}
